home *** CD-ROM | disk | FTP | other *** search
/ MacWorld UK 2000 March / MW_UK_2000_03.iso / Shareware World / Utilities / Text Processing / Alpha / Tcl / Modes / diffMode.tcl < prev    next >
Encoding:
Text File  |  1999-07-11  |  31.6 KB  |  1,121 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  Vince's Additions - an extension package for Alpha
  4.  # 
  5.  #  FILE: "diffMode.tcl"
  6.  #                                    created: 7/3/95 {11:15:02 pm} 
  7.  #                                last update: 8/7/99 {1:39:53 am} 
  8.  #  Author: Vince Darley
  9.  #  E-mail: <vince@santafe.edu>
  10.  #    mail: Division of Engineering and Applied Sciences, Harvard University
  11.  #          Oxford Street, Cambridge MA 02138, USA
  12.  #     www: <http://www.santafe.edu/~vince/>
  13.  #  
  14.  # improvements Copyright (c) 1997-1999  Vince Darley, all rights reserved
  15.  # 
  16.  #  Description: 
  17.  #  
  18.  #  Largely re-written Diff mode for Alpha.  Still under construction,
  19.  #  but already a lot better than the old one.  Basic features:
  20.  #  
  21.  #  A 'Diff' menu, which contains commonly used options.
  22.  #  
  23.  #  Uses Alpha's 'marks' so that you can patch diffs back and forth
  24.  #  between files without losing the correct location in the file.
  25.  #  (previously if you modified one of the original windows, all line
  26.  #  numbers after that would be incorrect)
  27.  #  
  28.  #  Limitations:
  29.  #  
  30.  #  Sadly a lot of Alpha's window manipulation commands only work
  31.  #  on the foremost window.  This means this code is slowed down a
  32.  #  lot because it often has to bring a window to the front before
  33.  #  reading/writing into it.  There is a flag to setup a hack which
  34.  #  helps with this, at the expense of colours in the windows.
  35.  # 
  36.  #  History:
  37.  # 
  38.  #  modified by  rev reason
  39.  #  -------- --- --- -----------
  40.  #  7/3/95   Pete? 1.0 original
  41.  #  3/9/97   VMD 2.0 much improved version
  42.  #  03/23/98 VMD and Jon Guyer 2.0-3.0 various fixes and Voodoo 
  43.  # ###################################################################
  44.  ##
  45.  
  46. # Usage: diff [-#] [-abBcdefhHilnNprstTuvw] [-C lines] [-F regexp] [-I regexp]
  47. #        [-L label [-L label]] [-S file] [-D symbol] [+ignore-blank-lines]
  48. #        [+context[=lines]] [+unified[=lines]] [+ifdef=symbol]
  49. #        [+show-function-line=regexp]
  50. #        [+speed-large-files] [+ignore-matching-lines=regexp] [+new-file]
  51. #        [+initial-tab] [+starting-file=file] [+text] [+all-text] [+ascii]
  52. #        [+minimal] [+ignore-space-change] [+ed] [+reversed-ed] [+ignore-case]
  53. #        [+print] [+rcs] [+show-c-function] [+binary] [+brief] [+recursive]
  54. #        [+report-identical-files] [+expand-tabs] [+ignore-all-space]
  55. #        [+file-label=label [+file-label=label]] [+version] path1 path2
  56.  
  57. alpha::mode Diff 3.1 diffMenu {*.diff *.patch} {diffMenu} {
  58.     alpha::package require AlphaTcl 7.2.1b5
  59.     addMenu diffMenu •288
  60.     namespace eval compare {}
  61.     menu::insert Utils submenu 0 compare
  62.     menu::insert compare items end "windows" "files…" "directories…"
  63.     hook::register requireOpenWindowsHook [list compare windows] 2
  64.     ensureset DiffSig DIFF
  65.     set Diff::handlers(Diff-mode) Diff::runInsideAlpha
  66.     ensureset Diff::handler Diff-mode
  67.     lunion varPrefs(Files) Diff::handler
  68.     # By default Alpha handles the results of diff internally using
  69.     # its Diff mode.  However add-on packages to Alpha can provide
  70.     # alternative Diff handlers.
  71.     newPref var Diff::handler "Diff-mode" global "" Diff::handlers array
  72. } uninstall {
  73.     file delete "$pkg_file"
  74.     file delete [file join ${HOME} Tools "GNU Diff"]
  75. } maintainer {
  76.     "Vince Darley" vince@santafe.edu <http://www.santafe.edu/~vince/>
  77. } help {file "Diff Help"}
  78.  
  79. array set DiffAppSignatures {
  80.     GnuDiff DIFF
  81. }
  82. array set DiffAppScripts {
  83.     GnuDiff {
  84.     {dosc -c $quotedSig -s $flags}
  85.     }
  86. }
  87.  
  88. proc diffMenu {} {}
  89.  
  90. # Generally best to use this setting, but some actions can be a bit
  91. # slower with it on.  Allows you to patch changes back and forth
  92. # between windows automatically, which is otherwise not possible
  93. newPref f useSophisticatedDiffMarking 1 Diff
  94. # A good idea, but can mess up window colours sometimes
  95. # (it's a bit of a hack)
  96. newPref f useFastWindowSwapping 1 Diff
  97. # Slows things down in that it has to scan through Alpha's list of marks
  98. # to find the correct positions for each window, but speeds things up
  99. # because it doesn't need to activate each window in turn.  Try it and see
  100. # for yourself.
  101. newPref f useMarksDontBringToFront 1 Diff
  102. # Up/Down arrows both scroll the diff window and synchronise the viewed
  103. # portion of text in the document windows
  104. newPref f synchroniseMoveAndView 1 Diff Diff::bindUpDown
  105. # You'll probably want this; may slow things down a bit though
  106. newPref f workaroundAlphaColourBug 1 Diff
  107. # Default lines of context to generate when asking Diff to do its magic
  108. newPref var linesOfContext 3 Diff
  109. # Other diff flags you want to send (ignore whitespace etc)
  110. newPref var diffFlags { } Diff
  111. # If you've imported a diff file from a Unix system, this option allows
  112. # you to use it with Alpha too.
  113. newPref f translatePathDelimiters 1 Diff
  114. # If you've imported a diff file from a different directory structure,
  115. # you may need to remove a given prefix so Alpha can find your files
  116. # correctly.
  117. newPref v removeFilePrefix "" Diff
  118.  
  119. Menu -n $diffMenu -p Diff::menuProc -M Diff {
  120.     "rerunDiff"
  121.     "(-"
  122.     "/<I<BpatchIntoLeftWindow"
  123.     "/<I<BpatchIntoRightWindow"
  124.     "(-"
  125.     "cleanUpAndCloseWindows"
  126.     "(-"
  127.     "locateLeftWindow"
  128.     "locateRightWindow"
  129.     "locateLeftDir"
  130.     "locateRightDir"
  131.     "parseDiffWin"
  132. }
  133. Bind 0x7b <z> Diff::patchIntoLeftWindow Diff
  134. Bind 0x7c <z> Diff::patchIntoRightWindow Diff
  135.  
  136. if {[info tclversion] < 8.0} {
  137.     # Bind manually due to bug
  138.     Bind 0x7b <oz> Diff::patchIntoLeftWindow Diff
  139.     Bind 0x7c <oz> Diff::patchIntoRightWindow Diff
  140. }
  141. # do the rest
  142. Bind '\r'        Diff::Select    Diff
  143. Bind '\t'        Diff::View    Diff
  144. Bind Kpad. <c>        Diff::Win
  145. Bind Enter        {Diff::Down;Diff::Select}    Diff
  146. Bind Kpad0        {Diff::Up;Diff::Select}    Diff
  147.  
  148. hook::register closeHook Diff::closing Diff
  149. hook::register openHook Diff::opening Diff
  150.  
  151. proc Diff::bindUpDown {} {
  152.     global DiffmodeVars
  153.     if {$DiffmodeVars(synchroniseMoveAndView)} {
  154.     catch {unBind down         Diff::Down Diff}
  155.     catch {unBind up         Diff::Up Diff}
  156.     Bind down        {Diff::Down;Diff::View}    Diff
  157.     Bind up        {Diff::Up;Diff::View}    Diff
  158.     } else {
  159.     catch {unBind down        {Diff::Down;Diff::View}    Diff}
  160.     catch {unBind up        {Diff::Up;Diff::View}    Diff}
  161.     Bind down         Diff::Down Diff
  162.     Bind up         Diff::Up Diff
  163.     }
  164. }
  165.  
  166. Diff::bindUpDown
  167.  
  168. proc Diff::menuProc {menu item} {
  169.     Diff::$item
  170. }
  171.  
  172. proc Diff::locateLeftWindow {} {
  173.     global Diff::1
  174.     set Diff::1 [getfile "Select your left (old) file:"]
  175.     Diff::Display Diff::1 1 0 1
  176.     Diff::setMarksUp
  177.     if {[info exists Diff::1]} {Diff::mark ${Diff::1} 1 ""}
  178.     Diff::diffWinFront
  179. }
  180.  
  181. proc Diff::locateRightWindow {} {
  182.     global Diff::2
  183.     set Diff::2 [getfile "Select your right (new) file:"]
  184.     Diff::Display Diff::2 0 0 1
  185.     Diff::setMarksUp
  186.     if {[info exists Diff::2]} {Diff::mark ${Diff::2} 0 ""}
  187.     Diff::diffWinFront
  188. }
  189.  
  190. proc Diff::locateLeftDir {} {
  191.     global Diff::leftDir
  192.     set Diff::leftDir [get_directory "Select your left (old) directory:"]
  193. }
  194. proc Diff::locateRightDir {} {
  195.     global Diff::rightDir
  196.     set Diff::rightDir [get_directory "Select your right (new) directory:"]
  197. }
  198.  
  199. proc Diff::rerunDiff {} {
  200.     global diffDir Diff::1 Diff::2
  201.     Diff::diffWinFront
  202.     catch {set d1 ${Diff::1}}
  203.     catch {set d2 ${Diff::2}}
  204.     killWindow
  205.     catch {set Diff::1 $d1}
  206.     catch {set Diff::2 $d2}
  207.     if {$diffDir} {
  208.     Diff::execute 1 {* Directory Comparison *}
  209.     } else {
  210.     Diff::files
  211.     }
  212. }
  213.  
  214. proc Diff::cleanUpAndCloseWindows {} {
  215.     global Diff::1 Diff::2 diffDir
  216.     if {![catch {bringToFront ${Diff::1}}]} {
  217.     removeAllMarks diff-*
  218.     shrinkFull
  219.     killWindow
  220.     }
  221.     
  222.     if {![catch {bringToFront ${Diff::2}}]} {
  223.     removeAllMarks diff-*
  224.     shrinkFull
  225.     killWindow
  226.     }
  227.     Diff::diffWinFront
  228.     killWindow
  229. }
  230.  
  231. proc Diff::closing {{name ""}} {
  232.     global Diff::array Diff::Marked Diff::1 Diff::2
  233.     foreach var [uplevel \#0 info vars Diff::array*] {
  234.     global $var
  235.     if {[uplevel \#0 array exists $var]} { unset $var }
  236.     }
  237.     catch {unset Diff::Marked}
  238.     catch {unset Diff::1}
  239.     catch {unset Diff::2}
  240. }
  241.  
  242. ## 
  243.  # -------------------------------------------------------------------------
  244.  # 
  245.  # "Diff::opening" --
  246.  # 
  247.  #  This procedure is called whenever we open a diff window, whether 
  248.  #  a '.diff' file, or whether a window produced by this mode using
  249.  #  'Diff::execute'.  We parse its contents.
  250.  # -------------------------------------------------------------------------
  251.  ##
  252. proc Diff::opening {name} {
  253.     global Diff::window DiffmodeVars Diff::leftDir Diff::rightDir
  254.     set Diff::window $name
  255.     set Diff::leftDir ""
  256.     set Diff::rightDir ""
  257.     
  258.     set files [Diff::getFiles [minPos]]
  259.     if {[file exists [set cur [stripNameCount [win::Current]]]]} {
  260.     set cur [file dirname $cur]
  261.     set f0 [lindex $files 0]
  262.     if {[file exists [file join $cur $f0]]} {
  263.         set Diff::leftDir $cur
  264.     }
  265.     set f1 [lindex $files 1]
  266.     if {[file exists [file join $cur $f1]]} {
  267.         set Diff::rightDir $cur
  268.     }
  269.     }
  270.  
  271.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  272.     Diff::parseDiffWin
  273.     }
  274. }
  275.  
  276. # ◊◊◊◊ Parsing diff information ◊◊◊◊ #
  277.  
  278. proc Diff::parseDiffWin {} {
  279.     Diff::diffWinFront
  280.     global diffDir Diff::window DiffmodeVars
  281.     
  282.     set pos [minPos]
  283.     while 1 {
  284.     set res [search -s -n -f 1 -r 1 "^(diff.*|\[^- \n\r\]+)(\r|\n|\$)" $pos]
  285.     if {$res != ""} {
  286.         set pos [pos::math [lindex $res 0] + 1]
  287.         # if we picked up a 'diff...' line in a context diff
  288.         if {[lookAt $pos] == "i" && [lookAt [nextLineStart [lindex $res 0]]] == "*"} {
  289.         continue
  290.         }
  291.         set t [getText [lindex $res 0] [pos::math [lindex $res 1] - 1]]
  292.         if {[regexp {^\*+$} $t]} {
  293.         set diffDir 1
  294.         # check if the file has changed
  295.         if {[string index [set tt [getText [prevLineStart $pos] $pos]] 0] != " " \
  296.           && [lookAt [pos::math $pos - 3]] != "-" } {
  297.             set to [lindex $tt 1]
  298.             regexp " (.*)\t" $tt "" to
  299.             set p [prevLineStart $pos]
  300.             regexp " (.*)\t" [getText [prevLineStart $p] $p] from
  301.             if {$DiffmodeVars(translatePathDelimiters)} {
  302.             set from [file::translatePathDelimiters $from]
  303.             set to [file::translatePathDelimiters $to]
  304.             }
  305.             lappend got [list "diff" $from $to]
  306.         }
  307.         set from [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} $pos]] 1]
  308.         set to [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} $pos]] 1]
  309.         lappend got "$from $to"
  310.         } else {
  311.         lappend got $t
  312.         }
  313.     } else {
  314.         break
  315.     }
  316.     }
  317.     set Diff::window [win::Current]
  318.     # now stored all diff items in the list 'got'
  319.     if {[info exists got]} {Diff::storeMarks $got}
  320.     Diff::diffWinFront
  321.     global tileTop tileWidth tileHeight tileLeft
  322.     set top [expr {$tileTop + $tileHeight - 178}]
  323.     sizeWin ${Diff::window} [expr {$tileWidth - 6}] 178
  324.     moveWin ${Diff::window} $tileLeft $top
  325.     
  326. }
  327.  
  328. proc Diff::storeMarks {diffs} {
  329.     global Diff::1 Diff::2 Diff::array
  330.     set suff ""
  331.     foreach m $diffs {
  332.     if {[regexp {^diff} $m]} {
  333.         set suff "/[file tail [lindex $m end]]"
  334.         global Diff::array${suff}
  335.         continue
  336.     }
  337.     set Diff::array${suff}($m) ""
  338.     }
  339. }
  340.  
  341. proc Diff::setMarksUp {{suff ""}} {
  342.     global Diff::array${suff}
  343.     foreach m [array names Diff::array$suff] {
  344.     set scanned [Diff::parseDiffString $m]
  345.     if {[scan $scanned "%s %f %f %f %f" \
  346.       char start1 end1 start2 end2] != 5} { error "Bad diff list!" }
  347.     if {$scanned != ""} {
  348.         set Diff::array${suff}($m) $scanned
  349.     }
  350.     }
  351. }
  352.  
  353. proc Diff::mark {win left {suff ""}} {
  354.     global Diff::array$suff DiffmodeVars
  355.     if {$win != ""} {
  356.     # Alpha somehow remembers the last mode in which it adjusts
  357.     # the window and so forgets all the colours if we cheat the
  358.     # mode switch.
  359.     if {$DiffmodeVars(workaroundAlphaColourBug)} {
  360.         bringToFront $win
  361.     } else {
  362.         Diff::BringToFront $win
  363.     }
  364.     # not strictly necessary, but cleaner
  365.     removeAllMarks diff-*
  366.     if {$left} {
  367.         foreach m [array names Diff::array$suff] {
  368.         scan [set Diff::array${suff}($m)] "%s %f %f" char start1 end1
  369.         setNamedMark "diff-$m" $start1 $start1 $end1
  370.         }
  371.     } else {
  372.         foreach m [array names Diff::array$suff] {
  373.         scan [set Diff::array${suff}($m)] "%s %f %f %f %f" char start1 end1 start2 end2
  374.         setNamedMark "diff-$m" $start2 $start2 $end2
  375.         }
  376.     }
  377.     }
  378. }
  379.  
  380. proc Diff::markUpWindow {diffs} {
  381.     alertnote "Currently a little obsolete; shouldn't be called!"
  382.     if {[info exists Diff::1]} {
  383.     Diff::BringToFront ${Diff::1}
  384.     # not strictly necessry, but cleaner
  385.     removeAllMarks diff-*
  386.     foreach m $diffs {
  387.         scan [set Diff::array($m)] "%s %f %f" char start1 end1
  388.         setNamedMark "diff-$m" $start1 $start1 $end1
  389.     }
  390.     }
  391.     if {[info exists Diff::2]} {
  392.     Diff::BringToFront ${Diff::2}
  393.     # not strictly necessry, but cleaner
  394.     removeAllMarks diff-*
  395.     foreach m $diffs {
  396.         scan [set Diff::array($m)] "%s %f %f %f %f" char start1 end1 start2 end2
  397.         setNamedMark "diff-$m" $start2 $start2 $end2
  398.     }
  399.     }
  400.     
  401. }
  402.  
  403. proc Diff::parseDiffString {text} {
  404.     global Diff::1 Diff::2
  405.     if {![regexp {[acd]} $text char]} {
  406.     # context sensitive
  407.     set char "c"
  408.     if {[scan $text "%d,%d %d,%d" one oned two twod] != 4} {
  409.         return
  410.     }
  411.     } else {
  412.     set res [split $text $char]
  413.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  414.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  415.     if {![info exists oned]} { set oned $one }
  416.     if {![info exists twod]} { set twod $two }
  417.     }
  418.     
  419.     if {[info exists Diff::1]} {
  420.     set res [list $char [rowColToPos -w ${Diff::1} $one 0]]
  421.     if {$char != "a"} {
  422.         lappend res [rowColToPos -w ${Diff::1} [expr {$oned + 1}] 0]
  423.     } else {
  424.         lappend res [rowColToPos -w ${Diff::1} $oned 1]
  425.     }
  426.     } else {
  427.     set res [list $char -1 -1]
  428.     }
  429.     
  430.     if {[info exists Diff::2]} {
  431.     lappend res [rowColToPos -w ${Diff::2} $two 0]
  432.     if {$char != "d"} {
  433.         lappend res [rowColToPos -w ${Diff::2} [expr {$twod + 1}] 0]
  434.     } else {
  435.         lappend res [rowColToPos -w ${Diff::2} $twod 1]
  436.     }
  437.     } else {
  438.     lappend res -1 -1
  439.     }
  440.     return $res
  441. }
  442.  
  443. proc Diff::parseDiffLine {text {is_pos 0}} {
  444.     if {$is_pos} {
  445.     set text [Diff::line $text]
  446.     }
  447.     return [Diff::parseDiffString $text]
  448. }
  449.  
  450. proc Diff::getFiles {pos} {
  451.     global DiffmodeVars
  452.     set llen [llength [set files [getText $pos [nextLineStart $pos]]]]
  453.     set files [lrange $files [expr {$llen -2}] end]
  454.     if {$DiffmodeVars(translatePathDelimiters)} {
  455.     set files [file::translatePathDelimiters $files]
  456.     }
  457.     return $files
  458. }
  459.  
  460. proc Diff::line {pos {f ""}} {
  461.     global diffDir Diff::window DiffmodeVars
  462.     if {$diffDir} {
  463.     if {$f != ""} {upvar $f files}
  464.     if {[lookAt $pos] == "*" || [catch {search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" $pos} res]} {
  465.         set p $pos
  466.         while 1 {
  467.         set res [search -s -f 0 -r 1 "^\\*+(\r|\n|\$)" $p]
  468.         set p [pos::math [lindex $res 0] - 2]
  469.         if {[lookAt [lineStart $p]] != " " && [lookAt $p] != "-"} break
  470.         }
  471.         regexp " (.*)\t" [getText [lineStart $p] $p] "" to
  472.         regexp " (.*)\t" [getText [prevLineStart $p] [lineStart $p]] "" from
  473.         if {[set pr $DiffmodeVars(removeFilePrefix)] != ""} {
  474.         regsub -all "/\./" $to "/" to
  475.         if {[string first $pr $to] == 0} {
  476.             set to [string range $to [string length $pr] end]
  477.         }
  478.         regsub -all "/\./" $from "/" from
  479.         if {[string first $pr $from] == 0} {
  480.             set from [string range $from [string length $pr] end]
  481.         }
  482.         }
  483.         set files [list $from $to]
  484.         set tfrom [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} [getPos]]] 1]
  485.         set tto [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} [getPos]]] 1]
  486.         set text "$tfrom $tto"        
  487.     } else {
  488.         set llen [llength [set files [eval getText $res]]]
  489.         set files [lrange $files [expr {$llen -2}] end]
  490.         set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  491.     }
  492.     if {$DiffmodeVars(translatePathDelimiters)} {
  493.         foreach ff $files {
  494.         lappend nfiles [file::translatePathDelimiters $ff]
  495.         }
  496.         set files $nfiles
  497.     }
  498.     set f [lindex $files end]
  499.     set suff "/[file tail $f]"
  500.     } else {
  501.     set suff ""
  502.     set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  503.     }
  504.     return "${text}${suff}"
  505. }
  506.  
  507. # ◊◊◊◊ Patching routines ◊◊◊◊ #
  508. proc Diff::patch {w1 w2 left} {
  509.     global DiffmodeVars
  510.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  511.     Diff::patchSophisticated $w1 $w2 $left
  512.     } else {
  513.     Diff::patchOld $w1 $w2 $left
  514.     }
  515. }
  516. proc Diff::patchSophisticated {ww1 ww2 left} {
  517.     upvar \#0 $ww1 w1
  518.     upvar \#0 $ww2 w2
  519.     set code [Diff::line [getPos]]
  520.     regexp {([^/]+)(.*)} $code "" mark suff
  521.     global Diff::array${suff}
  522.     if {![info exists w1]} { dialog::errorAlert "No such window" }
  523.     switch -- "[lindex [set Diff::array${suff}($mark)] 0]${left}" {
  524.     "c1" -
  525.     "c0" {
  526.         if {[info exists w2]} { 
  527.         Diff::BringToFront ${w2}
  528.         gotoMark "diff-$mark"
  529.         set text [getSelect]
  530.         } else {
  531.         # we assume the line is selected in the diff-win
  532.         if {$left} {
  533.             set p [selEnd]
  534.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  535.             set p [lindex $e 1]
  536.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  537.             set text [getText $p [lindex $e 0]]
  538.             regsub -all "\[\n\r\]> " $text "\r" text
  539.             set text [string range $text 1 end]
  540.         } else {
  541.             set p [selEnd]
  542.             set e [search -s -f 1 -r 1 {^---} $p]
  543.             set text [getText $p [lindex $e 0]]
  544.             regsub -all "\[\r\n\]< " $text "\r" text
  545.             set text [string range $text 1 end]
  546.         }
  547.         }
  548.         Diff::BringToFront ${w1}
  549.         gotoMark "diff-$mark"
  550.         replaceText [getPos] [selEnd] $text
  551.     }
  552.     "d1" -
  553.     "a0" {
  554.         Diff::BringToFront ${w1}
  555.         gotoMark "diff-$mark"
  556.         deleteText [getPos] [selEnd]
  557.     }
  558.     "a1" -
  559.     "d0" {
  560.         if {[info exists w2]} { 
  561.         Diff::BringToFront ${w2}
  562.         gotoMark "diff-$mark"
  563.         set text [getSelect]
  564.         } else {
  565.         # we assume the line is selected in the diff-win
  566.         if {$left} {
  567.             set p [selEnd]
  568.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  569.             set p [lindex $e 1]
  570.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  571.             set text [getText $p [lindex $e 0]]
  572.             regsub -all "\[\n\r\]> " $text "\r" text
  573.             set text [string range $text 1 end]
  574.         } else {
  575.             set p [selEnd]
  576.             set e [search -s -f 1 -r 1 {^---} $p]
  577.             set text [getText $p [lindex $e 0]]
  578.             regsub -all "\[\n\r\]< " $text "\r" text
  579.             set text [string range $text 1 end]
  580.         }
  581.         }
  582.         Diff::BringToFront ${w1}
  583.         gotoMark "diff-$mark"
  584.         nextLine
  585.         insertText -w ${w1} $text
  586.     }
  587.     default {
  588.         error "Didn't understand the diff to patch!"
  589.     }
  590.     
  591.     }
  592.     Diff::diffWinFront
  593. }
  594. proc Diff::patchOld {ww1 ww2 left} {
  595.     upvar \#0 $ww1 w1
  596.     upvar \#0 $ww2 w2
  597.     set code [Diff::line [getPos]]
  598.     if {[scan [Diff::parseDiffLine $code] "%s %f %f %f %f" \
  599.       char start1 end1 start2 end2] != 5} { return }
  600.     
  601.     switch $char${left} {
  602.     "c1" {
  603.         set text [getText -w ${w2} $start2 $end2]
  604.         bringToFront ${w1}
  605.         replaceText $start1 $end1 $text
  606.     }
  607.     "d1" {
  608.         bringToFront ${w1}
  609.         deleteText $start1 $end1
  610.     }
  611.     "a1" {
  612.         set text [getText -w ${w2} $start2 $end2]
  613.         set p [nextLineStart $start1]
  614.         # for some reason this single line won't work instead of the
  615.         # next two!
  616.         #select -w ${Diff::1} $p $p
  617.         bringToFront ${w1}
  618.         goto $p
  619.         insertText -w ${w1} $text
  620.     }
  621.     "c0" {
  622.         set text [getText -w ${w2} $start1 $end1]
  623.         bringToFront ${w1}
  624.         replaceText $start2 $end2 $text
  625.     }
  626.     "d0" {
  627.         set text [getText -w ${w2} $start1 $end1]
  628.         bringToFront ${w1}
  629.         goto $start2
  630.         nextLine
  631.         insertText $text
  632.     }
  633.     "a0" {
  634.         bringToFront ${w1}
  635.         deleteText $start2 $end2
  636.     }
  637.     }
  638.     message "Subsequent insertions will be screwed up"
  639. }
  640.  
  641. # In the diff-window, 'c' = cut from left, replace with given lines,
  642. # 'd' = delete from left, 'a' = add to left.
  643. proc Diff::patchIntoLeftWindow {} {
  644.     Diff::patch Diff::1 Diff::2 1
  645. }
  646.  
  647. proc Diff::patchIntoRightWindow {} {
  648.     Diff::patch Diff::2 Diff::1 0
  649. }
  650.  
  651. # ◊◊◊◊ Main comparison routines ◊◊◊◊ #
  652.  
  653. proc Diff::files {} {
  654.     global Diff::1 Diff::2
  655.     foreach f [list ${Diff::1} ${Diff::2}] {
  656.     if {[lsearch [winNames -f] $f] >= 0} {
  657.         getWinInfo -w $f arr
  658.         if {$arr(dirty)} {
  659.         bringToFront $f
  660.         if {![dialog::yesno "Save this window?"]} { error "Cancel"}
  661.         save
  662.         }
  663.     }
  664.     }
  665.     # make sure newer file is on the right
  666.     if {[file::secondIsOlder ${Diff::1} ${Diff::2}]} {
  667.     set d ${Diff::2}
  668.     set Diff::2 ${Diff::1}
  669.     set Diff::1 $d
  670.     unset d
  671.     }
  672.     Diff::run
  673. }
  674.  
  675. proc Diff::run {} {
  676.     global Diff::handler Diff::handlers
  677.     # call the registered procedure
  678.     [set Diff::handlers([set Diff::handler])]
  679. }
  680.  
  681. proc Diff::runInsideAlpha {} {
  682.     global Diff::1 Diff::2
  683.     Diff::Display Diff::1 1 0 1
  684.     Diff::Display Diff::2 0 0 1
  685.  
  686.     Diff::execute
  687. }
  688.  
  689. proc compare::directories {} {
  690.     global Diff::1 Diff::2
  691.     
  692.     set Diff::1 [get_directory -p "Select 'old' dir 1:"]
  693.     set Diff::2 [get_directory -p "Select 'new' dir 2:"]
  694.     
  695.     Diff::execute 1 {* Directory Comparison *}
  696. }
  697.  
  698. proc compare::files {} {
  699.     global Diff::1 Diff::2
  700.     
  701.     set Diff::1 [getfile "Select your 'old' file:"]
  702.     set Diff::2 [getfile "Select your 'new' file:"]
  703.     
  704.     Diff::files
  705. }
  706.  
  707. proc compare::windows {} {
  708.     global tileHeight tileWidth tileTop tileLeft
  709.     global Diff::1 Diff::2
  710.     
  711.     set wins [winNames -f]
  712.     if {[llength $wins] < 2} { message "Need 2 windows"; return }
  713.     
  714.     set Diff::1 [lindex $wins 0]
  715.     set Diff::2 [lindex $wins 1]
  716.     Diff::files
  717. }
  718.  
  719.  
  720. ## 
  721.  # -------------------------------------------------------------------------
  722.  # 
  723.  # "Diff::execute" --
  724.  # 
  725.  #  Modification of the original to optionally return the diff 
  726.  #  result, rather than opening it in a window
  727.  # 
  728.  # Results:
  729.  # 
  730.  #  Returns 1 if the files are the same and 0 if they differ
  731.  #  
  732.  #  If storeResult is true, the result of the diff operation is stored 
  733.  #  in the global Diff::result, rather than being opened in a window
  734.  # 
  735.  # --Version--Author------------------Changes-------------------------------
  736.  #    1.0     <keleher@cs.umd.edu> original
  737.  #    1.1     <j-guyer@nwu.edu> optionally return diff result in a global
  738.  #    1.2     <j-guyer@nwu.edu> flags set if files were open before compare
  739.  # -------------------------------------------------------------------------
  740.  ##
  741. proc Diff::execute {{isdir 0} {name {* File Comparison *}} {storeResult 0}} {
  742.     global DiffmodeVars \
  743.       Diff::1 Diff::2 win::Modes HOME diffDir Diff::result \
  744.       Diff::1Open Diff::2Open \
  745.       Diff::leftDir Diff::rightDir DiffSig
  746.     
  747.     set Diff::leftDir ""
  748.     set Diff::rightDir ""
  749.     set diffDir $isdir
  750.     
  751.     message "Launching 'GNU Diff'"
  752.     set flags $DiffmodeVars(diffFlags)
  753.     if {$DiffmodeVars(linesOfContext) != 0} {
  754.     append flags " -C $DiffmodeVars(linesOfContext)"
  755.     }
  756.     message "Starting diff…"
  757.     append flags " \"[stripNameCount ${Diff::1}]\" \"[stripNameCount ${Diff::2}]\""
  758.     set dtext [app::runScript Diff "Diff application" "" 1 0 $flags]
  759.     message "Starting diff…done"
  760.     
  761.     if {[lsearch [winNames -f] ${Diff::1}] >= 0} {
  762.     set Diff::1Open 1
  763.     } else {
  764.     set Diff::1Open 0
  765.     }
  766.     if {[lsearch [winNames -f] ${Diff::2}] >= 0} {
  767.     set Diff::2Open 1
  768.     } else {
  769.     set Diff::2Open 0
  770.     }
  771.     
  772.     if {![string length $dtext]} {
  773.     if {!$storeResult} {
  774.         alertnote    "No difference:\r${Diff::1}\r${Diff::2}"
  775.     }
  776.     return 0
  777.     } else {    
  778.     # If requested, return the diff result in Diff::result, 
  779.     # rather than opening a diff window
  780.     if {$storeResult} {
  781.         set Diff::result $dtext
  782.     } else {
  783.         Diff::diffWindow $dtext $name            
  784.     }
  785.     return 1
  786.     }
  787. }
  788.  
  789. proc Diff::displayAll {{name "* File Comparison *"}} {
  790.     global Diff::1 Diff::2 Diff::result
  791.     
  792.     Diff::Display Diff::1 1 0 1
  793.     Diff::Display Diff::2 0 0 1
  794.     Diff::diffWindow ${Diff::result} $name    
  795. }
  796.  
  797. proc Diff::diffWindow {diffText {name {* File Comparison *}}} {
  798.     global tileLeft tileTop tileWidth tileHeight    
  799.     
  800.     set top [expr {$tileTop + $tileHeight - 178}]
  801.     set n [new -n $name -g $tileLeft $top [expr {$tileWidth - 6}] 178 \
  802.       -m Diff -info "\r$diffText\r"]
  803.     select [minPos] [nextLineStart [minPos]]
  804.     Diff::opening $n
  805. }
  806.  
  807. # ◊◊◊◊ Moving around ◊◊◊◊ #
  808. proc Diff::Up {} {
  809.     Diff::move 0
  810. }
  811.  
  812. proc Diff::Down {} {
  813.     Diff::move 1
  814. }
  815.  
  816. proc Diff::move {dir} {
  817.     if {$dir} {
  818.     set pos [pos::math [getPos] + 1]
  819.     } else {
  820.     set pos [pos::math [getPos] - 1]
  821.     }
  822.     if {[catch {search -s -f $dir -r 1 -- "^\[^- \n\r\]+(\r|\n|\$)" $pos} res]} {
  823.     message "No more diffs"
  824.     return
  825.     }
  826.     set pos [lindex $res 0]
  827.     select $pos [nextLineStart $pos]    
  828.     display $pos
  829.     refresh
  830. }
  831.  
  832. proc Diff::Select {} {
  833.     global Diff::1 Diff::2 diffDir
  834.     
  835.     set text [getText [lineStart [getPos]] [pos::math [nextLineStart [getPos]] - 1]]
  836.     
  837.     if {![regexp {[acd]} $text char]} return
  838.     set res [split $text $char]
  839.     if {![scan [lindex $res 0] "%d" one]} return
  840.     if {![scan [lindex $res 1] "%d" two]} return
  841.     if {$one == 1} {incr one}
  842.     if {$two == 1} {incr two}
  843.     
  844.     if {$diffDir} {
  845.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  846.     set text [eval getText $res]
  847.     set len [llength $text]
  848.     set Diff::1 [lindex $text [expr {$len - 2}]]
  849.     set Diff::2 [lindex $text [expr {$len - 1}]]
  850.     }
  851.     Diff::Display Diff::1 1 [expr {$one - 1}] $diffDir
  852.     Diff::Display Diff::2 0 [expr {$two - 1}] $diffDir
  853.     
  854.     if {$diffDir} {
  855.     catch {bringToFront ${Diff::window}}
  856.     }
  857. }
  858.  
  859. proc Diff::Display {name left {row 0} {check 0}} {
  860.     upvar $name wname
  861.     if {![info exists wname]} {
  862.     if {$left} {
  863.         message "Diff window for left doesn't exist"
  864.     } else {
  865.         message "Diff window for right doesn't exist"
  866.     }
  867.     return
  868.     }
  869.     if {$check} {
  870.     set geo [Diff::Geo $left]
  871.     set res [lsearch [winNames -f] "$wname"];
  872.     if { $res < 0 } {
  873.         set res [lsearch [winNames -f] "$wname <*>"];
  874.     }
  875.     if { $res < 0} {
  876.         eval edit -g $geo [list $wname]
  877.         set wname [win::Current]
  878.     } else {
  879.         set wname [lindex [winNames -f] $res]
  880.         if {[getGeometry $wname] != $geo} {
  881.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  882.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  883.         }
  884.         if {$res > 2} {
  885.         bringToFront $wname
  886.         }
  887.     }
  888.     }
  889.     display -w $wname [rowColToPos -w $wname $row 0]
  890. }
  891.  
  892. proc Diff::viewSophisticated {} {
  893.     global Diff::1 Diff::2 diffDir DiffmodeVars Diff::Marked
  894.     global Diff::leftDir Diff::rightDir
  895.     
  896.     set text [Diff::line [getPos] files]
  897.  
  898.     if {$diffDir} {
  899.     set Diff::1 [lindex $files 0]
  900.     if {![file exists ${Diff::1}]} {
  901.         set Diff::1 [file join ${Diff::leftDir} ${Diff::1}]
  902.     }
  903.     if {![file exists ${Diff::1}]} {
  904.         if {${Diff::leftDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::1}]*"]] != -1)} {
  905.         set Diff::1 [lindex [winNames -f] $res]
  906.         } else {
  907.         unset Diff::1
  908.         }
  909.     } else {
  910.         if {[set res [lsearch [winNames -f] "${Diff::1}*"]] != -1} {
  911.         set Diff::1 [lindex [winNames -f] $res]
  912.         }
  913.     }
  914.     set Diff::2 [lindex $files 1]
  915.     if {![file exists ${Diff::2}]} {
  916.         set Diff::2 [file join ${Diff::rightDir} ${Diff::2}]
  917.     }
  918.     if {![file exists ${Diff::2}]} {
  919.         if {${Diff::rightDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::2}]*"]] != -1)} {
  920.         set Diff::2 [lindex [winNames -f] $res]
  921.         } else {
  922.         unset Diff::2
  923.         }
  924.     } else {
  925.         if {[set res [lsearch [winNames -f] "${Diff::2}*"]] != -1} {
  926.         set Diff::2 [lindex [winNames -f] $res]
  927.         }
  928.     }
  929.     }
  930.     regexp {([^/]+)(.*)} $text "" mark suff
  931.     if {![info exists "Diff::Marked($suff)"]} {
  932.     Diff::Display Diff::1 1 0 1
  933.     Diff::Display Diff::2 0 0 1
  934.     Diff::setMarksUp $suff
  935.     if {[info exists Diff::1]} {
  936.         Diff::mark ${Diff::1} 1 $suff
  937.         set Diff::Marked($suff) 1
  938.     }
  939.     if {[info exists Diff::2]} {
  940.         Diff::mark ${Diff::2} 0 $suff
  941.         set Diff::Marked($suff) 1
  942.     }
  943.     Diff::diffWinFront
  944.     }
  945.     set text $mark
  946.     
  947.     if {$DiffmodeVars(useMarksDontBringToFront)} {
  948.     if {![catch {mark::getRange diff-$text ${Diff::1}} range]} {
  949.         set beg [lindex $range 0]
  950.         set end [lindex $range 2]
  951.         Diff::displayLines ${Diff::1} $beg $end
  952.     }
  953.     
  954.     if {![catch {mark::getRange diff-$text ${Diff::2}} range]} {
  955.         set beg [lindex $range 0]
  956.         set end [lindex $range 2]
  957.         Diff::displayLines ${Diff::2} $beg $end
  958.     }
  959.     # we need this line because of an Alpha visual bug.
  960.     # Alpha will often draw the text in the wrong window when we 
  961.     # hit 'down'.  It does correct itself, but it looks silly.
  962.     Diff::diffWinFront
  963.     } else {            
  964.     if {![catch {Diff::BringToFront ${Diff::1}}]} {
  965.         gotoMark "diff-$text"
  966.     }
  967.     if {![catch {Diff::BringToFront ${Diff::2}}]} {
  968.         gotoMark "diff-$text"        
  969.     }
  970.     Diff::diffWinFront
  971.     }
  972. }
  973.  
  974. proc Diff::displayLines {win beg end} {
  975.     display -w $win $end
  976.     display -w $win [expr \
  977.       {[pos::compare $beg > [minPos]] \
  978.       ? [pos::math $beg - 1] : $beg}]
  979.     select -w $win $beg $end
  980.     if {[pos::compare $beg > [minPos]]} {
  981.     set beg [pos::math $beg - 1]
  982.     }
  983.     #display -w $win $beg
  984.     #refresh $win
  985. }
  986.  
  987. proc Diff::viewOld {} {
  988.     global Diff::1 Diff::2 diffDir 
  989.     
  990.     set text [Diff::line [getPos]]
  991.     if {![regexp {[acd]} $text char]} return
  992.     set res [split $text $char]
  993.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  994.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  995.     set on $one
  996.     set tw $two
  997.     if {$on == 1} {incr on}
  998.     if {$tw == 1} {incr tw}
  999.     if {![info exists oned]} {set oned $one}
  1000.     if {![info exists twod]} {set twod $two}
  1001.     
  1002.     if {$diffDir} {
  1003.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  1004.     set text [eval getText $res]
  1005.     set Diff::1 [lindex $text 1]
  1006.     set Diff::2 [lindex $text 2]
  1007.     }
  1008.     Diff::Sel Diff::1 [expr {$on - 1}] $one $oned 1
  1009.     Diff::Sel Diff::2 [expr {$tw - 1}] $two $twod 0
  1010.     set wins [lremove [lrange [winNames -f] 0 2] ${Diff::1} ${Diff::2}]
  1011.     set wins [lremove -glob $wins *Comparison*]
  1012.     if {$wins != ""} {
  1013.     bringToFront ${Diff::1}
  1014.     bringToFront ${Diff::2}
  1015.     }
  1016.     Diff::diffWinFront
  1017. }
  1018.  
  1019. proc Diff::View {} {
  1020.     global DiffmodeVars
  1021.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  1022.     Diff::viewSophisticated
  1023.     } else {
  1024.     Diff::viewOld
  1025.     }
  1026. }
  1027.  
  1028. ## 
  1029.  # -------------------------------------------------------------------------
  1030.  # 
  1031.  # "Diff::Sel" --
  1032.  # 
  1033.  #  This handles a name either with or without trailing '<n>' and fixes
  1034.  #  the given name if it isn't right.
  1035.  # -------------------------------------------------------------------------
  1036.  ##
  1037. proc Diff::Sel {wnamev ro row rowd left} {
  1038.     global diffDir
  1039.     upvar $wnamev wname
  1040.     if {$diffDir} {
  1041.     set geo [Diff::Geo $left]
  1042.     if {[set res [lsearch [winNames -f] "$wname*"]] < 0} {
  1043.         eval edit -g $geo [list $wname]
  1044.         set wname [win::Current]
  1045.     } else {
  1046.         set wname [lindex [winNames -f] $res]
  1047.         if {[getGeometry $wname] != $geo} {
  1048.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  1049.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  1050.         }
  1051.     }
  1052.     }
  1053.     display -w $wname [rowColToPos -w $wname $ro 0]
  1054.     select -w $wname [rowColToPos -w $wname $row 0] \
  1055.       [rowColToPos -w $wname [expr {$rowd + 1}] 0]
  1056. }
  1057.  
  1058. # ◊◊◊◊ Utilities ◊◊◊◊ #
  1059.  
  1060. proc Diff::Win {} {
  1061.     global win::Modes
  1062.     set files [winNames -f]
  1063.     set len [llength $files]
  1064.     for {set i 0} {$i < $len} {incr i} {
  1065.     if {[set win::Modes([lindex [winNames -f] $i])] == "Diff"} {
  1066.         bringToFront [lindex [winNames] $i]
  1067.         return
  1068.     }
  1069.     }
  1070.     beep
  1071.     message "No Diff window."
  1072. }
  1073.  
  1074. proc Diff::Geo {left} {
  1075.     global tileWidth tileHeight tileTop tileLeft
  1076.     
  1077.     set margin 4
  1078.     set width [expr {($tileWidth - $margin)/2}]
  1079.     set height [expr {$tileHeight - 200}]
  1080.     set hor $tileLeft
  1081.     
  1082.     if {!$left} {incr hor [expr {$width+$margin}]}
  1083.     
  1084.     return [list $hor $tileTop $width $height]
  1085. }
  1086.  
  1087. proc Diff::diffWinFront {} {
  1088.     global Diff::window
  1089.     catch {bringToFront ${Diff::window}}
  1090. }
  1091.  
  1092. ## 
  1093.  # -------------------------------------------------------------------------
  1094.  # 
  1095.  # "Diff::BringToFront" --
  1096.  # 
  1097.  #  Hack to make it quicker to switch between windows.  We often want
  1098.  #  the 'Diff' window to be in the front all the time, but have to
  1099.  #  bring others to the front temporarily for manipulation.  This proc
  1100.  #  brings a different window to the front more quickly by avoiding
  1101.  #  all mode-changing code.  Of course you should only call this proc
  1102.  #  when you will _very_ soon bring a different window to the front.
  1103.  # -------------------------------------------------------------------------
  1104.  ##
  1105. proc Diff::BringToFront {w} {
  1106.     global win::Modes DiffmodeVars
  1107.     if {$DiffmodeVars(useFastWindowSwapping)} {
  1108.     set oldm [set win::Modes($w)]
  1109.     set win::Modes($w) Diff
  1110.     if {[catch {bringToFront $w}]} {
  1111.         unset win::Modes($w)
  1112.         beep
  1113.         error "no such win"
  1114.     } else {
  1115.         set win::Modes($w) $oldm
  1116.     }
  1117.     } else {
  1118.     bringToFront $w
  1119.     }
  1120. }
  1121.